{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cfab0f01",
   "metadata": {},
   "source": [
    "## Text Splitters 文本分割器\n",
    "\n",
    "加载文档后，您通常会想要对其进行转换以更好地适合您的应用程序。最简单的例子是，您可能希望将长文档分割成更小的块，以适合模型的上下文窗口。 LangChain 有许多内置的文档转换器，可以轻松地拆分、组合、过滤和以其他方式操作文档。\n",
    "\n",
    "当您想要处理长文本时，有必要将该文本分割成块。这听起来很简单，但这里存在很多潜在的复杂性。理想情况下，您希望将语义相关的文本片段保留在一起。 “语义相关”的含义可能取决于文本的类型。本笔记本展示了实现此目的的几种方法。\n",
    "\n",
    "在较高层面上，文本分割器的工作原理如下：\n",
    "- 将文本分成小的、具有语义意义的块（通常是句子）。\n",
    "- 开始将这些小块组合成一个更大的块，直到达到一定的大小（通过某些函数测量）。\n",
    "- 一旦达到该大小，请将该块设为自己的文本片段，然后开始创建具有一些重叠的新文本块（以保持块之间的上下文）。\n",
    "\n",
    "### HTMLHeaderTextSplitter\n",
    "\n",
    "“MarkdownHeaderTextSplitter”、“HTMLHeaderTextSplitter”是一个“结构感知”分块器，它在元素级别拆分文本，并为每个与任何给定块“相关”的标题添加元数据。它可以逐个元素返回块，或者将元素与相同的元数据组合起来，目的是 (a) 保持相关文本在语义上（或多或少）分组，以及 (b) 保留文档结构中编码的上下文丰富的信息。它可以与其他文本分割器一起使用，作为分块管道的一部分。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "7fb4f7fe",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Document(page_content='<!DOCTYPE html>\\n<html lang=\"zh\">\\n\\t<head>\\n\\t\\t<meta charset=\"utf-8\" />\\n\\t\\t<base href=\"../../../\" />\\n\\t\\t<script src=\"page.js\"></script>\\n\\t\\t<link type=\"text/css\" rel=\"stylesheet\" href=\"page.css\" />\\n\\t</head>\\n\\t<body>\\n\\t\\t<h1>动画系统（[name]）</h1>\\n\\n\\t\\t<h2>概述</h2>\\n\\n\\t\\t<p class=\"desc\">\\n\\t\\t\\t在three.js动画系统中，您可以为模型的各种属性设置动画：\\n\\t\\t\\t[page:SkinnedMesh]（蒙皮和装配模型）的骨骼，morph targets（变形目标），\\n\\t\\t\\t不同的材料属性（颜色，不透明度，布尔运算），可见性和变换。动画属性可以淡入、淡出、交叉淡化和扭曲。\\n\\t\\t\\t在相同或不同物体上同时发生的动画的权重和时间比例的变化可以独立地进行。\\n\\t\\t\\t相同或不同物体的动画也可以同步发生。\\n\\t\\t\\t<br /><br />\\n\\t\\t\\t为了在一个同构系统中实现所有这一切，\\n\\t\\t\\tthree.js的动画系统[link:https://github.com/mrdoob/three.js/issues/6881 在2015年彻底改变]（注意过时的信息！），\\n\\t\\t\\t它现在有一个与Unity/虚幻4引擎类似的架构。此页面简要阐述了这个系统中的主要组件以及它们如何协同工作。\\n\\t\\t</p>\\n\\n\\t\\t<h3>动画片段（Animation Clips）</h3>\\n\\n\\t\\t<p class=\"desc\">\\n\\t\\t\\t如果您已成功导入3D动画对象（无论它是否有骨骼或变形目标或两者皆有都不要紧）——\\n\\t\\t\\t例如使用[link:https://github.com/KhronosGroup/glTF-Blender-IO glTF Blender exporter]（glTF Blender导出器）</a>\\n\\t\\t\\t从Blender导出它并使用[page:GLTFLoader]将其加载到three.js场景中\\n\\t\\t\\t—— 其中一个响应字段应该是一个名为“animations”的数组，\\n\\t\\t\\t其中包含此模型的[page:AnimationClip AnimationClips]（请参阅下面可用的加载器列表）。\\n\\t\\t\\t<br /><br />\\n\\t\\t\\t每个*AnimationClip*通常保存对象某个活动的数据。\\n\\t\\t\\t举个例子，假如mesh是一个角色，可能有一个AnimationClip实现步行循环，\\n\\t\\t\\t第二个AnimationClip实现跳跃，第三个AnimationClip实现闪避等等。\\n\\t\\t</p>\\n\\n\\t\\t<h3>关键帧轨道（Keyframe Tracks）</h3>\\n\\n\\t\\t<p class=\"desc\">\\n\\t\\t\\t在这样的*AnimationClip*里面，每个动画属性的数据都存储在一个单独的[page:KeyframeTrack]中。\\n\\t\\t\\t假设一个角色对象有[page:Skeleton]（骨架），\\n\\t\\t\\t一个关键帧轨道可以存储下臂骨骼位置随时间变化的数据，\\n\\t\\t\\t另一个轨道追踪同一块骨骼的旋转变化，第三个追踪另外一块骨骼的位置、转角和尺寸，等等。\\n\\t\\t\\t应该很清楚，AnimationClip可以由许多这样的轨道组成。\\n\\t\\t\\t<br /><br />\\n\\t\\t\\t假设模型具有morph Targets（变形目标）——\\n\\t\\t\\t例如一个变形目标显示一个笑脸，另一个显示愤怒的脸 ——\\n\\t\\t\\t每个轨道都持有某个变形目标在AnimationClip运行期间产生的[page:Mesh.morphTargetInfluences]（变形目标影响）如何变化的信息。\\n\\t\\t</p>\\n\\n\\t\\t<h3>动画混合器（Animation Mixer）</h3>\\n\\n\\t\\t<p class=\"desc\">\\n\\t\\t\\t存储的数据仅构成动画的基础 —— 实际播放由[page:AnimationMixer]控制。\\n\\t\\t\\t你可以想象这不仅仅是动画的播放器，而是作为硬件的模拟，如真正的调音台，可以同时控制和混合若干动画。\\n\\t\\t</p>\\n\\n\\t\\t<h3>动画行为（Animation Actions）</h3>\\n\\n\\t\\t<p class=\"desc\">\\n\\t\\t\\t*AnimationMixer*本身只有很少的（大体上）属性和方法，\\n\\t\\t\\t因为它可以通过[page:AnimationAction AnimationActions]来控制。\\n\\t\\t\\t通过配置*AnimationAction*，您可以决定何时播放、暂停或停止其中一个混合器中的某个*AnimationClip*，\\n\\t\\t\\t这个*AnimationClip*是否需要重复播放以及重复的频率，\\n\\t\\t\\t是否需要使用淡入淡出或时间缩放，以及一些其他内容（例如交叉渐变和同步）。\\n\\t\\t</p>\\n\\n\\t\\t<h3>动画对象组（Animation Object Groups）</h3>\\n\\n\\t\\t<p class=\"desc\">\\n\\t\\t\\t如果您希望一组对象接收共享的动画状态，则可以使用[page:AnimationObjectGroup]。\\n\\t\\t</p>\\n\\n\\t\\t<h3>支持的格式和加载器（Supported Formats and Loaders）</h3>\\n\\n\\t\\t<p class=\"desc\">\\n\\t\\t\\t请注意，并非所有模型格式都包含动画（尤其是OBJ，没有），\\n\\t\\t\\t而且只有某些three.js加载器支持[page:AnimationClip AnimationClip]序列。\\n\\t\\t\\t以下几个<i>确实</i>支持此动画类型：\\n\\t\\t</p>\\n\\n\\t\\t\\t<ul>\\n\\t\\t\\t\\t<li>[page:ObjectLoader THREE.ObjectLoader]</li>\\n\\t\\t\\t\\t<li>THREE.BVHLoader</li>\\n\\t\\t\\t\\t<li>THREE.ColladaLoader</li>\\n\\t\\t\\t\\t<li>THREE.FBXLoader</li>\\n\\t\\t\\t\\t<li>[page:GLTFLoader THREE.GLTFLoader]</li>\\n\\t\\t\\t\\t<li>THREE.MMDLoader</li>\\n\\t\\t\\t</ul>\\n\\n\\t\\t<p class=\"desc\">\\n\\t\\t\\t请注意，3ds max和Maya当前无法直接导出多个动画（这意味着动画不是在同一时间线上）到一个文件中。\\n\\t\\t</p>\\n\\n\\t\\t<h2>范例</h2>\\n\\n\\t\\t<code>\\n\\t\\tlet mesh;\\n\\n\\t\\t// 新建一个AnimationMixer, 并取得AnimationClip实例列表\\n\\t\\tconst mixer = new THREE.AnimationMixer( mesh );\\n\\t\\tconst clips = mesh.animations;\\n\\n\\t\\t// 在每一帧中更新mixer\\n\\t\\tfunction update () {\\n\\t\\t\\tmixer.update( deltaSeconds );\\n\\t\\t}\\n\\n\\t\\t// 播放一个特定的动画\\n\\t\\tconst clip = THREE.AnimationClip.findByName( clips, \\'dance\\' );\\n\\t\\tconst action = mixer.clipAction( clip );\\n\\t\\taction.play();\\n\\n\\t\\t// 播放所有动画\\n\\t\\tclips.forEach( function ( clip ) {\\n\\t\\t\\tmixer.clipAction( clip ).play();\\n\\t\\t} );\\n\\t\\t</code>\\n\\n\\t</body>\\n</html>\\n', metadata={'source': './html/Animation-system.html'})]"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_community.document_loaders import TextLoader\n",
    "\n",
    "loader = TextLoader(\"./html/Animation-system.html\",encoding=\"utf8\")\n",
    "doc = loader.load()\n",
    "doc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "148f579d",
   "metadata": {},
   "outputs": [],
   "source": [
    "headers_to_split_on = [\n",
    "    (\"h1\", \"Header 1\"),\n",
    "    (\"h2\", \"Header 2\"),\n",
    "    (\"h3\", \"Header 3\"),\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "4cd4cdd2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# pip install -qU langchain-text-splitters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "370eaa1f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Document(page_content='在three.js动画系统中，您可以为模型的各种属性设置动画： [page:SkinnedMesh]（蒙皮和装配模型）的骨骼，morph targets（变形目标）， 不同的材料属性（颜色，不透明度，布尔运算），可见性和变换。动画属性可以淡入、淡出、交叉淡化和扭曲。 在相同或不同物体上同时发生的动画的权重和时间比例的变化可以独立地进行。 相同或不同物体的动画也可以同步发生。 为了在一个同构系统中实现所有这一切， three.js的动画系统[link:https://github.com/mrdoob/three.js/issues/6881 在2015年彻底改变]（注意过时的信息！）， 它现在有一个与Unity/虚幻4引擎类似的架构。此页面简要阐述了这个系统中的主要组件以及它们如何协同工作。', metadata={'Header 1': '动画系统（[name]）', 'Header 2': '概述'}),\n",
       " Document(page_content='如果您已成功导入3D动画对象（无论它是否有骨骼或变形目标或两者皆有都不要紧）—— 例如使用[link:https://github.com/KhronosGroup/glTF-Blender-IO glTF Blender exporter]（glTF Blender导出器） 从Blender导出它并使用[page:GLTFLoader]将其加载到three.js场景中 —— 其中一个响应字段应该是一个名为“animations”的数组， 其中包含此模型的[page:AnimationClip AnimationClips]（请参阅下面可用的加载器列表）。 每个*AnimationClip*通常保存对象某个活动的数据。 举个例子，假如mesh是一个角色，可能有一个AnimationClip实现步行循环， 第二个AnimationClip实现跳跃，第三个AnimationClip实现闪避等等。', metadata={'Header 1': '动画系统（[name]）', 'Header 2': '概述', 'Header 3': '动画片段（Animation Clips）'}),\n",
       " Document(page_content='在这样的*AnimationClip*里面，每个动画属性的数据都存储在一个单独的[page:KeyframeTrack]中。 假设一个角色对象有[page:Skeleton]（骨架）， 一个关键帧轨道可以存储下臂骨骼位置随时间变化的数据， 另一个轨道追踪同一块骨骼的旋转变化，第三个追踪另外一块骨骼的位置、转角和尺寸，等等。 应该很清楚，AnimationClip可以由许多这样的轨道组成。 假设模型具有morph Targets（变形目标）—— 例如一个变形目标显示一个笑脸，另一个显示愤怒的脸 —— 每个轨道都持有某个变形目标在AnimationClip运行期间产生的[page:Mesh.morphTargetInfluences]（变形目标影响）如何变化的信息。', metadata={'Header 1': '动画系统（[name]）', 'Header 2': '概述', 'Header 3': '关键帧轨道（Keyframe Tracks）'}),\n",
       " Document(page_content='存储的数据仅构成动画的基础 —— 实际播放由[page:AnimationMixer]控制。 你可以想象这不仅仅是动画的播放器，而是作为硬件的模拟，如真正的调音台，可以同时控制和混合若干动画。', metadata={'Header 1': '动画系统（[name]）', 'Header 2': '概述', 'Header 3': '动画混合器（Animation Mixer）'}),\n",
       " Document(page_content='*AnimationMixer*本身只有很少的（大体上）属性和方法， 因为它可以通过[page:AnimationAction AnimationActions]来控制。 通过配置*AnimationAction*，您可以决定何时播放、暂停或停止其中一个混合器中的某个*AnimationClip*， 这个*AnimationClip*是否需要重复播放以及重复的频率， 是否需要使用淡入淡出或时间缩放，以及一些其他内容（例如交叉渐变和同步）。', metadata={'Header 1': '动画系统（[name]）', 'Header 2': '概述', 'Header 3': '动画行为（Animation Actions）'}),\n",
       " Document(page_content='如果您希望一组对象接收共享的动画状态，则可以使用[page:AnimationObjectGroup]。', metadata={'Header 1': '动画系统（[name]）', 'Header 2': '概述', 'Header 3': '动画对象组（Animation Object Groups）'}),\n",
       " Document(page_content='请注意，并非所有模型格式都包含动画（尤其是OBJ，没有）， 而且只有某些three.js加载器支持[page:AnimationClip AnimationClip]序列。 以下几个确实支持此动画类型：  \\n[page:ObjectLoader THREE.ObjectLoader] THREE.BVHLoader THREE.ColladaLoader THREE.FBXLoader [page:GLTFLoader THREE.GLTFLoader] THREE.MMDLoader  \\n请注意，3ds max和Maya当前无法直接导出多个动画（这意味着动画不是在同一时间线上）到一个文件中。', metadata={'Header 1': '动画系统（[name]）', 'Header 2': '概述', 'Header 3': '支持的格式和加载器（Supported Formats and Loaders）'})]"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_text_splitters import HTMLHeaderTextSplitter\n",
    "html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)\n",
    "html_header_splits = html_splitter.split_text(doc[0].page_content)\n",
    "html_header_splits"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "4afe4ffc",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\a4154\\AppData\\Roaming\\Python\\Python310\\site-packages\\transformers\\utils\\hub.py:124: FutureWarning: Using `TRANSFORMERS_CACHE` is deprecated and will be removed in v5 of Transformers. Use `HF_HOME` instead.\n",
      "  warnings.warn(\n",
      "d:\\ProgramData\\anaconda3\\lib\\site-packages\\torch\\_utils.py:776: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly.  To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage()\n",
      "  return self.fget.__get__(instance, owner)()\n"
     ]
    }
   ],
   "source": [
    "from langchain_community.vectorstores import FAISS\n",
    "from langchain_community.embeddings.huggingface import HuggingFaceEmbeddings\n",
    "embeddings_path = \"D:\\\\ai\\\\download\\\\bge-large-zh-v1.5\"\n",
    "embeddings = HuggingFaceEmbeddings(model_name=embeddings_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "4876799c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<langchain_community.vectorstores.faiss.FAISS at 0x1e3d6943a30>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vectorStoreDB = FAISS.from_documents(html_header_splits,embedding=embeddings)\n",
    "vectorStoreDB"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "417667e9",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "template =\"\"\"\n",
    "只根据以下文档回答问题：\n",
    "{context}\n",
    "\n",
    "问题：{question}\n",
    "\"\"\"\n",
    "\n",
    "prompt = ChatPromptTemplate.from_template(template)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "e3a1c6f7",
   "metadata": {},
   "outputs": [],
   "source": [
    "retriever = vectorStoreDB.as_retriever(\n",
    "    search_type=\"mmr\",\n",
    "    search_kwargs={\"k\": 2}\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "9dbfadea",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Document(page_content='*AnimationMixer*本身只有很少的（大体上）属性和方法， 因为它可以通过[page:AnimationAction AnimationActions]来控制。 通过配置*AnimationAction*，您可以决定何时播放、暂停或停止其中一个混合器中的某个*AnimationClip*， 这个*AnimationClip*是否需要重复播放以及重复的频率， 是否需要使用淡入淡出或时间缩放，以及一些其他内容（例如交叉渐变和同步）。', metadata={'Header 1': '动画系统（[name]）', 'Header 2': '概述', 'Header 3': '动画行为（Animation Actions）'}),\n",
       " Document(page_content='如果您希望一组对象接收共享的动画状态，则可以使用[page:AnimationObjectGroup]。', metadata={'Header 1': '动画系统（[name]）', 'Header 2': '概述', 'Header 3': '动画对象组（Animation Object Groups）'})]"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "docs = retriever.get_relevant_documents(\"请说说如何控制动画的播放和暂停\")\n",
    "docs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "83471570",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'context': [Document(page_content='*AnimationMixer*本身只有很少的（大体上）属性和方法， 因为它可以通过[page:AnimationAction AnimationActions]来控制。 通过配置*AnimationAction*，您可以决定何时播放、暂停或停止其中一个混合器中的某个*AnimationClip*， 这个*AnimationClip*是否需要重复播放以及重复的频率， 是否需要使用淡入淡出或时间缩放，以及一些其他内容（例如交叉渐变和同步）。', metadata={'Header 1': '动画系统（[name]）', 'Header 2': '概述', 'Header 3': '动画行为（Animation Actions）'}),\n",
       "  Document(page_content='如果您希望一组对象接收共享的动画状态，则可以使用[page:AnimationObjectGroup]。', metadata={'Header 1': '动画系统（[name]）', 'Header 2': '概述', 'Header 3': '动画对象组（Animation Object Groups）'})],\n",
       " 'question': '请说说如何控制动画的播放和暂停？'}"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_core.runnables import RunnableParallel,RunnablePassthrough\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "\n",
    "outputParser = StrOutputParser()\n",
    "\n",
    "setup_and_retrieval = RunnableParallel(\n",
    "    {\n",
    "        \"context\":retriever,\n",
    "        \"question\":RunnablePassthrough()\n",
    "    }\n",
    ")\n",
    "\n",
    "result = setup_and_retrieval.invoke(\"请说说如何控制动画的播放和暂停？\")\n",
    "result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "0fb4afc7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI, OpenAI\n",
    "openai_api_key = \"EMPTY\"\n",
    "openai_api_base = \"http://127.0.0.1:1234/v1\"\n",
    "model = ChatOpenAI(\n",
    "    openai_api_key=openai_api_key,\n",
    "    openai_api_base=openai_api_base,\n",
    "    temperature=0.3,\n",
    ")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "4495dbc3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'要控制动画的播放、暂停或停止，您需要使用[page:AnimationAction]。通过配置*AnimationAction*，您可以决定何时播放、暂停或停止其中一个混合器中的某个*AnimationClip*。'"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain = setup_and_retrieval | prompt | model | outputParser\n",
    "chain.invoke(\"请说说如何控制动画的播放和暂停？\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15f7c16a",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
